package biolabs.query;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import biolabs.core.BioObject;
import biolabs.core.Experiment;

public class QueryBuilder implements Query {

	/**
	 * Returns a new query that selects all bio-object in the hierarchy.
	 * This is equivalent to {@code get(DESCENDANT_OR_SELF)}.
	 */
	public static QueryBuilder get() {
		return get(Axis.DESCENDANT_OR_SELF);
	}

	/**
	 * Returns a new query that selects all bio-objects
	 * along the given axis from the root of the hierarchy.
	 */
	public static QueryBuilder get(Axis axis) {
		return new QueryBuilder(axis);
	}

	// selection axes
	private final List<Axis> axes = new ArrayList<Axis>();

	// filter predicates
	private final ArrayList<Predicate> predicates = new ArrayList<Predicate>();

	private CompiledQuery compiled = null;

	private QueryBuilder(Axis axis) {
		axes.add(axis);
		predicates.add(Predicate.TRUE);
	}

	/**
	 * Selects the bio-objects by navigation from currently
	 * selected bio-objects along the given axis.
	 * @param axis axis along which to navigate
	 * @return this query
	 */
	public QueryBuilder navigate(Axis axis) {
		compiled = null;
		axes.add(axis);
		predicates.add(Predicate.TRUE);
		return this;
	}

	/**
	 * Filters the set of currently selected bio-objects -
	 * retains only those bio-objects that satisfy the given predicate.
	 */
	public QueryBuilder filter(Predicate p) {
		compiled = null;
		int last = predicates.size()-1;
		Predicate q = new Predicate.And(predicates.get(last), p);
		predicates.set(last, q);
		return this;
	}

	/**
	 * Compiles this query. Compiled query will execute faster
	 * when executed multiple times.
	 * @return the compiled version of this query.
	 */
	@Override
	public CompiledQuery compile() {
		if(compiled == null)
			compiled = new CompiledQuery(axes, predicates);
		return compiled;
	}

	/**
	 * Executes this query on the given experiment.
	 * The read lock is acquired on the experiment
	 * while the query is being executed.
	 * <p>The query is compiled before executing. If the query is
	 * executed multiple times, you should consider compiling it
	 * (see {@link #compile()}) and execute the compiled query
	 * to avoid multiple compilations.</p>
	 * @param exp experiment to execute the query on.
	 * @return the result set of the query.
	 * @throws QueryExecutionException if the query execution threw an exception
	 */
	@Override
	public Collection<BioObject> execute(Experiment exp) throws QueryExecutionException {
		return compile().execute(exp);
	}

	@Override
	public Collection<BioObject> execute(Experiment exp, BioObject queryRoot) throws QueryExecutionException {
		return compile().execute(exp, queryRoot);
	}
}
